Liquidware's Stratusphere UX captures millions of rows of digital-workspace telemetry. Its REST API could surface any of it, if you already knew the schema. I built the interface that made that power point-and-click.
Stratusphere UX monitors digital workspaces and VDI estates, collecting a relentless stream of performance telemetry: UX scores, login delay, app load time, CPU queue, pagefile pressure, disk IOPS, session latency, and dozens more metrics, captured per user, per machine, per application, per hour.
All of it was already reachable through a REST API that returned JSON or CSV. The catch was the cost of entry. To ask a question, a customer had to know which inspector id mapped to the data they wanted, which basis key to pivot on, and the exact column names buried in the underlying schema. In practice that meant reading documentation, hand-assembling a JSON payload, and hoping the URL came back with something useful.
The result was a capability that shipped with the product but rarely left the hands of the people who had built it. The brief was to close that gap: let any customer interrogate their own data confidently, without teaching them the database.
I owned the front end end to end, from the query model and state architecture through to the shipped UI:
The same query, asked two ways. The builder did not add capability to the API, it removed the prerequisites for using it.
GET /lwl/api?json={
"inspector": "0", ← which id?
"basis": "machine_groups", ← which key?
"columns": "ux_score,
cpu_queue,
memory_used", ← exact names?
"date": "2 hours",
"limit": "0"
}
# Prerequisites:
# schema documentation
# inspector id reference
# valid column spellings
# trial and error
The friction. Every question started with research. A typo in a column name returned nothing, with no hint as to why.
// 1. Pick an Inspector (what kind of data) ▸ Summary ▸ Applications ▸ Remote Display ▸ Network ▸ Extension ▸ Alerts // 2. Choose a lookup basis (how to pivot) ☑ Machine Groups ☐ Users (ID) ☐ Machines ☐ User Name // 3. Refine columns, filters, sorting, date ☑ ux_score ☑ cpu_queue ☑ memory_used // 4. Builder emits the payload, live: { "inspector":"0", "basis":"machine_groups", "columns":"ux_score,cpu_queue,memory_used", "date":"2 hours", "limit":"0" } [ Launch ] [ Copy ] [ Save as favorite ]
The shift. The schema became a set of labelled choices. Results render immediately, and the generated JSON and URL stay visible so power users can still take them elsewhere.
Rather than mirroring the API's parameter list, the builder walks the user down a funnel: what kind of data, how to pivot it, then how to sharpen it.
An Inspector scopes the question to a domain: users, machines, applications, remote display, network, extensions or alerts. One choice eliminates most of the schema before the user sees it.
A lookup basis decides the grain of the answer. Multiple bases can be combined to increase detail, with searchable basis names in list or grid view for large estates.
Dedicated tabs for columns, filters, sorting, output and date turn the broad set into a precise one, with related metric groups selectable together instead of column by column.
Inspector metadata is large, slow to fetch and rarely changes. Query state is small, volatile and shared across seven tabs. Those two facts drove the whole design.
CRUD operations for saved queries, favorites and history. The only layer that knows about builder-specific endpoints, so query persistence evolved without touching transport code.
Query composition and validation, publishing changes over RxJS Subjects. Components subscribe to what they care about instead of calling each other, which kept coupling low across the tabbed composer.
Raw HTTP with retry logic and localStorage caching. Caching inspector metadata locally made the composer feel instant on return visits and offline-tolerant when the hub was slow.
Alongside it, a centralized NgRx store split into three slices, with 20+ reducer handlers and async effects for session verification, inspector metadata and basis-name fetching.
On top of that foundation: dynamic theming via DOM stylesheet replacement with PrimeNG ::ng-deep overrides, an i18n pipeline backed by JSON locale files with runtime locale switching, and favorite queries made shareable through URL-encoded query parameters.
The builder never hides its work. As choices are made, the JSON and URL update in place and the grid below re-renders. Click any screen to enlarge.






API Builder ships inside Stratusphere and is reachable from any hub. Version 2 extended the model with related metric column groups, importable and saveable API calls, and a full history of previous queries.
The interesting design problem here was not the visuals, it was deciding what to hide. Stratusphere's schema is genuinely large, and the temptation is to expose all of it behind search. Leading with the Inspector meant the first click eliminated most of the schema, so every subsequent choice stayed small enough to make confidently.
A modular single-page application with lazy components and scroll-position restoration, built to stay responsive while rendering very wide result grids.